﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Device.Location;
using System.Linq;
using System.Reflection;
using System.Windows.Input;
using BingleMaps.Extensions;
using BingleMaps.Models;
using BingleMaps.Services;
using GalaSoft.MvvmLight.Command;
using Microsoft.Phone.Controls.Maps.Platform;

namespace BingleMaps.ViewModels
{
	public class MainViewModel : ViewModelBase
	{
		#region Commands

		public ICommand SwitchFromToLocationsCommand { get; set; }

		#endregion


		#region Constructors

		public MainViewModel()
		{
			SwitchFromToLocationsCommand = new RelayCommand(() =>
			{
				var loc = FromDirectionPoint;
				FromDirectionPoint = ToDirectionPoint;
				ToDirectionPoint = loc;
			});
			GeoWatcher.PositionChanged += (s, e) => CurrentLocation = GeoWatcher.Position.Location;

			// Test data for design mode.
			if (!IsInDesignMode)
			{
				return;
			}
			UserPinLog = new UserPinLog();
			UserPinLog.Pins.Add(
				new UserPin
				{
					Address = "South Dowling St, NSW 2017",
					Color = SettingsViewModel.DefaultPushpinColor.Value,
					Icon = SettingsViewModel.DefaultPushpinIcon.Value,
					Key = new Guid("4596fb13-aeda-4608-97f0-c1d688ce2eea"),
					Location = new Location { Latitude = -33.91, Longitude = 151.21 },
					Title = "this is a very long name, to test a good max height of the name box of user pin page"
				}
				);
			TemporaryPins = new ObservableCollection<UserPin>
			{
				new UserPin
				{
					Address = "South Dowling St, NSW 2017",
					Color = SettingsViewModel.DefaultPushpinColor.Value,
					Icon = SettingsViewModel.DefaultPushpinIcon.Value,
					Key = new Guid("4596fb13-aeda-4608-97f0-c1d688ce2eeb"),
					Location = new Location { Latitude = -35, Longitude = 151.21 },
					Title = "this is a very long name, just to mess around",
					IsTemporary = true
				}
			};
			Itineraries = new Collection<ItineraryItem>
			{
				new ItineraryItem
				{
					Distance = 0.1D,
					Instruction = "DepartSouth Dowling St toward O'Dea Ave",
					Location = new Location
					{
						Latitude = -33.91,
						Longitude = 151.21
					}
				}
			};
			MapCenter = new GeoCoordinate { Latitude = -33.91, Longitude = 151.21 };
			ZoomLevel = 8;
			EditingUserPin = UserPinLog.Pins.First();
		}

		#endregion


		#region Public Properties

		private GeoCoordinateWatcher GeoWatcher
		{
			get { return _geoWatcher ?? (_geoWatcher = new GeoCoordinateWatcher(GeoPositionAccuracy.High) { MovementThreshold = 10D }); }
		}

		private GeoCoordinateWatcher _geoWatcher;


		public Version CurrentVersion
		{
			get { return _currentVersion ?? (_currentVersion = new Version(Assembly.GetExecutingAssembly().FullName.Split('=')[1].Split(',')[0])); }
		}

		private Version _currentVersion;

		public SettingsViewModel Settings { get; set; }


		public bool IsFollowingCurrentLocation
		{
			get { return _isFollowingCurrentLocation; }
			set
			{
				if (value == _isFollowingCurrentLocation)
				{
					return;
				}
				_isFollowingCurrentLocation = value;
				RaisePropertyChanged(IsFollowingCurrentLocationPropertyName);
			}
		}

		private bool _isFollowingCurrentLocation;
		public const string IsFollowingCurrentLocationPropertyName = "IsFollowingCurrentLocation";


		public IEnumerable<ItineraryItem> Itineraries
		{
			get { return _itineraries; }
			set { SetProperty(ref _itineraries, value, ItinerariesPropertyName); }
		}

		private IEnumerable<ItineraryItem> _itineraries;
		private const string ItinerariesPropertyName = "Itineraries";

		public SearchQuery SearchQuery
		{
			get { return _searchQuery; }
			set { SetProperty(ref _searchQuery, value, SearchQueryPropertyName); }
		}

		private SearchQuery _searchQuery;
		private const string SearchQueryPropertyName = "SearchQuery";


		public DirectionPoint FromDirectionPoint
		{
			get { return _fromDirectionPoint; }
			set { SetProperty(ref _fromDirectionPoint, value, FromDirectionPointPropertyName); }
		}

		private DirectionPoint _fromDirectionPoint;
		private const string FromDirectionPointPropertyName = "FromDirectionPoint";


		public DirectionPoint ToDirectionPoint
		{
			get { return _toDirectionPoint; }
			set { SetProperty(ref _toDirectionPoint, value, ToDirectionPointPropertyName); }
		}

		private DirectionPoint _toDirectionPoint;
		private const string ToDirectionPointPropertyName = "ToDirectionPoint";

		public double ZoomLevel
		{
			get { return _zoomLevel; }
			set { SetProperty(ref _zoomLevel, value, ZoomLevelPropertyName); }
		}

		private double _zoomLevel;
		private const string ZoomLevelPropertyName = "ZoomLevel";

		public GeoCoordinate MapCenter
		{
			get { return _mapCenter; }
			set
			{
				SetProperty(ref _mapCenter, value, MapCenterPropertyName);
				if (value == CurrentLocation || Settings == null || !Settings.EnableLocationService)
				{
					return;
				}
				IsFollowingCurrentLocation = false;
			}
		}

		private GeoCoordinate _mapCenter;
		private const string MapCenterPropertyName = "MapCenter";

		public GeoCoordinate CurrentLocation
		{
			get { return _currentLocation; }
			set
			{
				SetProperty(ref _currentLocation, value, CurrentLocationPropertyName, (l1, l2) => l1 == l2 || (l1 != null && l2 != null && l1.Latitude == l2.Latitude && l1.Longitude == l2.Longitude));
				if (IsFollowingCurrentLocation)
				{
					MapCenter = CurrentLocation;
				}
			}
		}

		private GeoCoordinate _currentLocation;
		public const string CurrentLocationPropertyName = "CurrentLocation";


		public UserPinLog UserPinLog
		{
			get { return _userPinLog; }
			set { SetProperty(ref _userPinLog, value, UserPinLogPropertyName); }
		}

		private UserPinLog _userPinLog;
		private const string UserPinLogPropertyName = "UserPinLog";

		public ObservableCollection<UserPin> TemporaryPins
		{
			get { return _temporaryPins; }
			set { SetProperty(ref _temporaryPins, value, TemporaryPinsPropertyName); }
		}

		private ObservableCollection<UserPin> _temporaryPins;
		private const string TemporaryPinsPropertyName = "TemporaryPins";

		public UserPin HeldUserPin
		{
			get { return _heldUserPin; }
			set { SetProperty(ref _heldUserPin, value, HeldUserPinPropertyName, UserPinExtensions.Equals); }
		}

		private UserPin _heldUserPin;
		private const string HeldUserPinPropertyName = "HeldUserPin";

		public UserPin EditingUserPin
		{
			get { return _editingUserPin; }
			set
			{
				if (UserPinExtensions.Equals(_editingUserPin, value))
				{
					return;
				}
				if (_editingUserPin != null)
				{
					_editingUserPin.IsEditing = false;
				}
				_editingUserPin = value;
				if (value != null)
				{
					value.IsEditing = true;
					MapCenter = value.Location;
				}
				RaisePropertyChanged(EditingUserPinPropertyName);
			}
		}

		private UserPin _editingUserPin;
		private const string EditingUserPinPropertyName = "EditingUserPin";

		public IEnumerable<Location> RouteLocations
		{
			get { return _routeLocations; }
			set { SetProperty(ref _routeLocations, value, RouteLocationsPropertyName); }
		}

		private IEnumerable<Location> _routeLocations;
		private const string RouteLocationsPropertyName = "RouteLocations";

		#endregion


		#region Public Functions

		public void Sleep()
		{
			GeoWatcher.Stop();
			LocalServices.SaveLocalSetting("TemporaryPins", TemporaryPins);
			LocalServices.SaveLocalSetting("UserPinLog", UserPinLog);
			LocalServices.SaveLocalSetting("EditingUserPinKey", EditingUserPin == null ? Guid.Empty : EditingUserPin.Key);
			LocalServices.SaveLocalSetting("ZoomLevel", ZoomLevel);
			LocalServices.SaveLocalSetting("MapCenter", MapCenter);
			LocalServices.SaveLocalSetting("FromDirectionPoint", FromDirectionPoint);
			LocalServices.SaveLocalSetting("ToDirectionPoint", ToDirectionPoint);
			LocalServices.SaveLocalSetting(RouteLocationsPropertyName, _routeLocations);
			LocalServices.SaveLocalSetting(ItinerariesPropertyName, Itineraries);
			LocalServices.SaveLocalSetting("SearchQuery", SearchQuery.Input);
			LocalServices.SaveLocalSetting("Version", Globals.CurrentVersion.ToString());
			LocalServices.SaveLocalSetting("Settings", Settings);
		}

		public void Wake(Action<Version> oldVersionAction = null)
		{
			Version saveVersion = null;
			var setting = LocalServices.LoadLocalSetting("Version");
			if (setting is string)
			{
				saveVersion = new Version((String)setting);
			}

			// Do not load from save if the save version is too old.
			if (LocalServices.HasLocalData && (saveVersion == null || saveVersion < Globals.BackwardCompatibleVersion))
			{
				// Clear settings.
				LocalServices.ClearLocalSettings();

				// Execute external action to deal with old save version.
				if (oldVersionAction != null)
				{
					oldVersionAction(saveVersion);
				}
			}

			// Continue loading from save if save version is compatible.
			if (ZoomLevel == default(double))
			{
				setting = LocalServices.LoadLocalSetting("ZoomLevel");
				if (setting is double && (double)setting != default(double))
				{
					ZoomLevel = (double)setting;
				}
				else
				{
					ZoomLevel = Globals.DefaultZoomLevel;
				}
			}
			if (MapCenter == null)
			{
				MapCenter = LocalServices.LoadLocalSetting("MapCenter") as GeoCoordinate;
			}

			if (UserPinLog == null)
			{
				setting = LocalServices.LoadLocalSetting("UserPinLog");
				if (setting is UserPinLog)
				{
					UserPinLog = (UserPinLog)setting;
				}
				else
				{
					UserPinLog = new UserPinLog();
				}
			}

			if (TemporaryPins == null)
			{
				setting = LocalServices.LoadLocalSetting("TemporaryPins");
				if (setting is ObservableCollection<UserPin>)
				{
					TemporaryPins = (ObservableCollection<UserPin>)setting;
				}
				else
				{
					TemporaryPins = new ObservableCollection<UserPin>();
				}
			}

			if (EditingUserPin == null)
			{
				setting = LocalServices.LoadLocalSetting("EditingUserPinKey");
				if (setting is Guid)
				{
					var key = (Guid)setting;
					EditingUserPin = UserPinLog.Pins.Union(TemporaryPins).FirstOrDefault(p => p.Key == key);
				}
			}

			if (FromDirectionPoint == null)
			{
				setting = LocalServices.LoadLocalSetting("FromDirectionPoint");
				if (setting is DirectionPoint)
				{
					FromDirectionPoint = (DirectionPoint)setting;
				}
				else
				{
					FromDirectionPoint = new DirectionPoint();
				}
			}
			if (ToDirectionPoint == null)
			{
				setting = LocalServices.LoadLocalSetting("ToDirectionPoint");
				if (setting is DirectionPoint)
				{
					ToDirectionPoint = (DirectionPoint)setting;
				}
				else
				{
					ToDirectionPoint = new DirectionPoint();
				}
			}
			if (RouteLocations == null)
			{
				setting = LocalServices.LoadLocalSetting(RouteLocationsPropertyName);
				if (setting is IEnumerable<Location>)
				{
					RouteLocations = (IEnumerable<Location>)setting;
				}
			}
			if (Itineraries == null)
			{
				setting = LocalServices.LoadLocalSetting(ItinerariesPropertyName);
				if (setting is IEnumerable<ItineraryItem>)
				{
					Itineraries = (IEnumerable<ItineraryItem>)setting;
				}
			}
			if (SearchQuery == null)
			{
				SearchQuery = new SearchQuery { Input = LocalServices.LoadLocalSetting("SearchQuery") as string };
			}
			if (Settings == null)
			{
				setting = LocalServices.LoadLocalSetting("Settings");
				if (setting is SettingsViewModel)
				{
					Settings = (SettingsViewModel)LocalServices.LoadLocalSetting("Settings");
				}
				else
				{
					Settings = new SettingsViewModel();
				}
				if (Settings.EnableLocationService)
				{
					GeoWatcher.Start();
				}
				Settings.PropertyChanged += (s, e) =>
				{
					if (e.PropertyName != "EnableLocationService")
					{
						return;
					}
					if (Settings.EnableLocationService)
					{
						GeoWatcher.Start();
					}
					else
					{
						GeoWatcher.Stop();
						CurrentLocation = null;
					}
				};
			}
		}

		#endregion
	}
}
